// Copyright (c) 1996-2002 John Lyon-Smith. All rights reserved.

#pragma once

#include <wxtl\SyncObject.h>
#include <wxtl\MeteredSection.h>
#include <wxtl\Event.h>
#include <wxtl\FileMap.h>

#pragma warning(push)
#pragma warning(disable:4786)  // 'identifier' : identifier was truncated to 'number' characters in the debug information
#pragma warning(disable:4200)  // 'identifier' : identifier was truncated to 'number' characters in the debug information

namespace wxtl
{
	TCHAR const kszFastIpcFileMapObjFmt[] = _T("wxtl.FastIpc.FileMap.%s");
	TCHAR const kszFastIpcMutexObjFmt[] = _T("wxtl.FastIpc.Mutex.%s");
	TCHAR const kszFastIpcAddEventObjFmt[] = _T("wxtl.FastIpc.Event.Add.%s");
	TCHAR const kszFastIpcRemoveEventObjFmt[] = _T("wxtl.FastIpc.Event.remove.%s");

	class FastIpc
	{
	public:
		FastIpc() {}
		virtual ~FastIpc() 
		{
			close();
		}

		bool create(const TCHAR* pIpcName, ULONG uMaxEntrySize, ULONG uNumEntries)
		{
			_ASSERT(pIpcName != NULL);
			
			SYSTEM_INFO si;
			
			// Allocate in units of the system page size
			GetSystemInfo(&si);
			
			DWORD dwPageSize = si.dwPageSize;
			ULONG uSize = (sizeof(QUEUE_HEADER) + (sizeof(ENTRY_HEADER) + uMaxEntrySize) * uNumEntries + 
				(dwPageSize - 1)) / dwPageSize * dwPageSize;
			TCHAR sz[_MAX_PATH];
			
			_stprintf_s(sz,MAX_PATH, kszFastIpcMutexObjFmt, pIpcName);

			if (!m_ms.create(sz))  // Initially locked
				return false;

			_stprintf_s(sz,MAX_PATH, kszFastIpcAddEventObjFmt, pIpcName);

			if (!m_evtAdd.create(false, false, sz))
			{
				close();
				return false;
			}

			_stprintf_s(sz,MAX_PATH, kszFastIpcRemoveEventObjFmt, pIpcName);

			if (!m_evtRemove.create(false, false, sz))
			{
				close();
				return false;
			}

			_stprintf_s(sz,MAX_PATH, kszFastIpcFileMapObjFmt, pIpcName);

			if (!m_fmap.create(uSize, NULL, sz))
			{
				close();
				return false;
			}

			QUEUE_HEADER* pHdr = GetQueueHdrPtr();

			pHdr->uEntries = 0;
			#ifdef _DEBUG
			pHdr->uQueueSize = uSize - sizeof(QUEUE_HEADER) - sizeof(ULONG);  // Save a guard DWORD
			#else
			pHdr->uQueueSize = uSize - sizeof(QUEUE_HEADER);
			#endif
			pHdr->offHead = pHdr->offTail = GetBottomOff();

			#ifdef _DEBUG
			// Set guard DWORD with something recognizable
			*(ULONG*)GetPtr(GetTopOff()) = 0xDEADBEEF;
			#endif

			m_ms.leave();  // Allow the data to flow...

			return true;
		}

		bool open(const TCHAR* pIpcName)
		{
			_ASSERT(pIpcName != NULL);
			
			TCHAR sz[_MAX_PATH];
			
			_stprintf_s(sz,_MAX_PATH, kszFastIpcMutexObjFmt, pIpcName);

			if (!m_ms.open(sz))
				return false;

			_stprintf_s(sz,_MAX_PATH, kszFastIpcAddEventObjFmt, pIpcName);

			if (!m_evtAdd.open(sz))
			{
				close();
				return false;
			}

			_stprintf_s(sz,_MAX_PATH, kszFastIpcRemoveEventObjFmt, pIpcName);

			if (!m_evtRemove.open(sz))
			{
				close();
				return false;
			}

			_stprintf_s(sz,_MAX_PATH, kszFastIpcFileMapObjFmt, pIpcName);

			if (!m_fmap.open(sz))
			{
				close();
				return false;
			}

			return true;
		}

		void close()
		{
			m_evtAdd.close();
			m_evtRemove.close();
			m_fmap.close();
			m_ms.close();
		}

		bool add(BYTE* pData, ULONG uLen, DWORD dwWaitTime = INFINITE)
		{
			if (!m_ms.tryEnter(dwWaitTime))
				return false;
			
			BYTE *pFree = Reserve(uLen);

			if (pFree != NULL)
			{
				memcpy(pFree, pData, uLen);
				m_ms.leave();
				m_evtAdd.set();
				return true;
			}
			else
			{
				m_ms.leave();
				return false;
			}
		}

		bool remove(DWORD dwWaitTime = INFINITE)
		{
			if (!m_ms.tryEnter(dwWaitTime))
				return false;

			Unreserve();

			m_evtRemove.set();

			m_ms.leave();

			return true;
		}

		bool lock(BYTE *&pData, UINT_PTR &uLen, DWORD dwWaitTime = INFINITE)
		{
			if (!m_ms.tryEnter(dwWaitTime))
				return false;

			QUEUE_HEADER* pHdr = GetQueueHdrPtr();

			if (pHdr->uEntries == 0)
			{
				m_ms.leave();
				return false;
			}

			ENTRY_HEADER* pHead = GetEntryHdrPtr(pHdr->offHead);

			pData = pHead->abyData;
			uLen = pHead->uDataSize;
			
			// IMPORTANT: unlock must be called to release the metered section!

			return true;
		}

		bool unlock()
		{
			// Check the guard bytes on the last page
			_ASSERTE(*(ULONG*)GetPtr(GetTopOff()) == 0xDEADBEEF);

			return m_ms.leave();
		}

		UINT_PTR size()
		{
			UINT_PTR uEntries;
			ExchangePtr<UINT_PTR>(&uEntries, GetQueueHdrPtr()->uEntries);
			return uEntries;
		}
		
		SyncObject *getAddEvent() { return &m_evtAdd; }
		SyncObject *getRemoveEvent() { return &m_evtRemove; }

	#if defined(_DEBUG)
		void dump()
		{
			QUEUE_HEADER* pHdr = GetQueueHdrPtr();
			
			/*
			WXTRACE2("FastIpc::QUEUE_HEADER {uQueueSize=0x%08x, uEntries=0x%08x, offHead=0x%08x, offTail=0x%08x }\n",
				pHdr->uQueueSize,
				pHdr->uEntries,
				pHdr->offHead,
				pHdr->offTail);
			*/

			ENTRY_HEADER* pEntry = GetEntryHdrPtr(pHdr->offHead);

			for (ULONG_PTR i = pHdr->uEntries; i > 0; i--)
			{			
				/*
				WXTRACE2("FastIpc::ENTRY_HEADER { off=0x%08x, uEntrySize=0x%08x, uDataSize=0x%08x }\n",
					GetOff(pEntry), 
					pEntry->uEntrySize,
					pEntry->uDataSize);
				*/
			
				pEntry = GetEntryHdrPtr(GetOff(pEntry) + pEntry->uEntrySize);
				
				// See if we wrap around 
				if (pEntry == GetEntryHdrPtr(GetTopOff()))
					pEntry = GetEntryHdrPtr(GetBottomOff());
			}
			
			/*
			WXTRACE2("FastIpc::GUARD_DWORD { off=0x%08x, value=0x%08x }\n", 
				GetTopOff(), *(ULONG*)GetPtr(GetTopOff()));
			*/
		}
	#else
		void dump() {}
	#endif

	protected:
		BYTE *Reserve(ULONG uDataLen)
		{
			QUEUE_HEADER* pHdr = GetQueueHdrPtr();
			ULONG uEntrySize = align<ULONG>(uDataLen + sizeof(ENTRY_HEADER), sizeof(ULONG));
			ENTRY_HEADER* pEntry = NULL;
			UINT_PTR uRemain;
			UINT_PTR offEntry = 0;

			if (pHdr->uEntries == 0)
			{
				// Is request bigger than the total amount of space available?
				if (uEntrySize > pHdr->uQueueSize)
					return NULL;

				_ASSERTE(pHdr->offHead == pHdr->offTail);
				
				pEntry = GetEntryHdrPtr(pHdr->offTail);
			}
			else if (pHdr->offTail >= pHdr->offHead)
			{
				// Jump over current tail block
				pEntry = GetEntryHdrPtr(pHdr->offTail);
				pEntry = GetEntryHdrPtr(pHdr->offTail + pEntry->uEntrySize);
				
				offEntry = GetOff(pEntry);
				
				uRemain = GetTopOff() - offEntry;

				// Does enough space exists between tail and end of memory?
				if (uEntrySize > uRemain)
				{
					// No...
					UINT_PTR uSaveRemain = uRemain;

					// Check if enough space exists before head
					uRemain = pHdr->offHead - GetBottomOff();

					if (uEntrySize > uRemain)
						return NULL;
					
					// Make old tail fill remaining memory
					GetEntryHdrPtr(pHdr->offTail)->uEntrySize += uSaveRemain;

					// Wrap new entry
					pEntry = GetEntryHdrPtr(GetBottomOff());
					offEntry = GetOff(pEntry);
				}

				pHdr->offTail = offEntry;
			}
			else
			{
				// Jump over current tail block
				pEntry = GetEntryHdrPtr(pHdr->offTail);
				pEntry = GetEntryHdrPtr(pHdr->offTail + pEntry->uEntrySize);
				
				offEntry = GetOff(pEntry);
				
				// Check if enough space exists before head
				uRemain = pHdr->offHead - offEntry;

				if (uEntrySize > uRemain)
					return NULL;
				
				pHdr->offTail = offEntry;
			}

			// We have to have picked an entry location
			_ASSERTE(pEntry != NULL);

			pHdr->uEntries++;

			// Write length as header to data
			pEntry->uDataSize = uDataLen;

			// Write real data size
			pEntry->uEntrySize = uEntrySize;

			return pEntry->abyData;
		}

		void Unreserve()
		{
			QUEUE_HEADER* pHdr = GetQueueHdrPtr();

			if (pHdr->offHead == pHdr->offTail)
			{
				// Queue has become empty (one entry was left)
				pHdr->offHead = pHdr->offTail = GetBottomOff();
				pHdr->uEntries = 0;
			}
			else if (pHdr->uEntries != 0)
			{
				// Advance head pointer to next entry
				pHdr->offHead += GetEntryHdrPtr(pHdr->offHead)->uEntrySize;

				// If we wrap around 
				if (pHdr->offHead == GetTopOff())
					pHdr->offHead = GetBottomOff();

				pHdr->uEntries--;
			}
		}

		struct QUEUE_HEADER
		{
			UINT_PTR uQueueSize;	// Size of queue memory (bytes)
			UINT_PTR uEntries;		// Current entries in queue
			UINT_PTR offHead;	// Relative head pointer
			UINT_PTR offTail;	// Relative tail pointer
		};

		struct ENTRY_HEADER
		{
			UINT_PTR uEntrySize;	// Total size of entry, rounded to DWORD boundary
			UINT_PTR uDataSize;	// Actual data size, minus header and padding bytes
			BYTE abyData[];		// First data byte
		};

		VOID* GetPtr(UINT_PTR p)
		{
			return m_fmap.data() + p;
		}

		UINT_PTR GetOff(VOID* p)
		{
			return (BYTE*)p - m_fmap.data();
		}

		QUEUE_HEADER* GetQueueHdrPtr()
		{
			return (QUEUE_HEADER*)GetPtr(0);
		}
		
		ENTRY_HEADER* GetEntryHdrPtr(UINT_PTR off)
		{
			return (ENTRY_HEADER*)GetPtr(off);
		}
		
		UINT_PTR GetBottomOff() 
		{ 
			return sizeof(QUEUE_HEADER); 
		}

		UINT_PTR GetTopOff() 
		{ 
			return GetBottomOff() + GetQueueHdrPtr()->uQueueSize;
		}

	// Data	
		FileMap m_fmap;
		MeteredSection m_ms;
		Event m_evtAdd;
		Event m_evtRemove;
	};
}

#pragma warning(pop)